/**
 *  Copyright 2011 Marco Berri - marcoberri@gmail.com
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations under the License.
 **/
package com.mbfasttrack.service;

import com.mbfasttrack.model.Hardware;
import com.mbfasttrack.model.Loc;
import com.mbfasttrack.model.Media;
import com.mbfasttrack.model.Point;
import com.mbfasttrack.model.Track;
import com.mbfasttrack.model.TrackPoint;
import com.mbfasttrack.model.User;
import com.mbfasttrack.utils.DateTimeUtil;
import com.mbfasttrack.utils.GpsUtil;
import com.mbfasttrack.utils.HttpUtil;
import com.mbfasttrack.utils.StringUtil;
import com.mbfasttrack.utils.ZipUtil;
import com.mongodb.gridfs.GridFS;
import com.mongodb.gridfs.GridFSInputFile;
import com.ximpleware.AutoPilot;
import com.ximpleware.NavException;
import com.ximpleware.VTDGen;
import com.ximpleware.VTDNav;
import com.ximpleware.XPathEvalException;
import com.ximpleware.XPathParseException;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.bson.types.ObjectId;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.util.WebUtils;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

/**
 * @author Marco Berri marcoberri@gmail.com http://tecnicume.blogspot.com
 */
@Service("TrackService")
@Transactional
public class TrackService extends Base {

    /**
     * 
     * @param t 
     * @return
     */
    public Track add(Track t) {


        try {

            String newFileName;
            GridFS gfsPhoto = new GridFS(mongoTemplate.getDb(), "FileTrack");
            GridFSInputFile gfsFile;
            System.err.print("t.getFileImported() : " + t.getFileImported());
            //se arrivo da un blocco di import
            if (t.getFileImported() != null) {
                newFileName = t.getFileImported().getName().toLowerCase();
                gfsFile = gfsPhoto.createFile(IOUtils.toInputStream(t.getFileImported().toString()));
                //gfsFile.setContentType(t.getFileImported().getContentType());

            } else {
                newFileName = t.getFile().getOriginalFilename().toLowerCase();
                gfsFile = gfsPhoto.createFile(t.getFile().getInputStream());
                gfsFile.setContentType(t.getFile().getContentType());
            }


            System.err.print("newFileName : " + newFileName);
            gfsFile.setFilename(newFileName);

            gfsFile.save();

            File tmp_dir = WebUtils.getTempDir(this.context);
            File filetemp = new File(tmp_dir + "/" + newFileName);

            System.err.print("filetemp : " + filetemp);

            if (filetemp.exists()) {
                filetemp.delete();
            }

            t.getFile().transferTo(filetemp);
            t.setFileId(gfsFile.getId());


            t.setMaxAltitude(0.0);
            t.setMinAltitude(0.0);
            t.setMaxLat(0.0);
            t.setMinLat(0.0);
            t.setMaxLon(0.0);
            t.setMinLon(0.0);

            if (t.getHardware() != null) {

                Hardware h = hardwareRepository.findOneBySlug(t.getHardware());
                if (h != null) {
                    t.setHardwareLink(h);
                    t.setHardware(null);
                }
            }



            if (newFileName.endsWith(".gpx")) {
                t = addGpx(filetemp.toString(), t);
            } else if (newFileName.endsWith(".kml")) {
                t = addKml(filetemp.toString(), t);
            } else if (newFileName.endsWith(".tcx")) {
            }


            //salvo tutti i track point

            if (filetemp.isFile()) {
                filetemp.delete();
            }

            t.setFile(null);
            if (t.getName() == null) {
                t.setName("" + System.currentTimeMillis());
            }

            String slug = StringUtil.normalize(t.getName());


            if (trackRepository.findOneBySlug(slug) != null) {
                slug += "-" + DateTimeUtil.getNowWithFormat("yyyy-MM-dd");
            }

            t.setSlug(slug);

            //parso i tag
            if (t.getTags() != null && !"".equalsIgnoreCase(t.getTags().trim())) {
                String[] split_tags = t.getTags().split(",");
                for (String tag_name : split_tags) {
                    System.err.println(tag_name);
                   // t.addTagLink(tagService.add(tag_name, 0));
                }
            }


            List<Point> elaboratePoint = t.getTrackpoint().getPoints();

            t.setStartpoint(elaboratePoint.get(0));
            t.setDatestop(elaboratePoint.get(elaboratePoint.size() - 1).getTime());
            t.setDatestart(elaboratePoint.get(0).getTime());
            //calcolo la distanza per assegnarla ad ogni punto tra il punto e il suo parent
            elaboratePoint = trackPointService.calculateDistance(elaboratePoint);
            //calcolo il tempo tra un punto e l'altro
            elaboratePoint = trackPointService.calculateTime(elaboratePoint);

            t.setDistance(sumDistance(elaboratePoint));
            t.setRealelevation(calculateRealElevation(elaboratePoint));
            t.setElevation(calculateElevation(elaboratePoint));

            t.getTrackpoint().setPoints(elaboratePoint);


            //genero la preview con google maps e salvo nei media il file
            t = trackRepository.save(t);

            trackPointRepository.save(t.getTrackpoint());

            //salvo le statistiche per l'utente
            User u = t.getUser();
            u.setTotDistance(u.getTotDistance() + t.getDistance());
            u.setTotElevation(u.getTotElevation() + t.getElevation());
            u.setTotRealElevation(u.getTotRealElevation() + t.getRealelevation());
            u.setTotTracks(u.getTotTracks() + 1);

            //se kml non posso calcolarlo
            if (!newFileName.endsWith(".kml")) {
                u.setTotTime(u.getTotTime() + (t.getDatestop().getTime() - t.getDatestart().getTime()));
            }


            userRepository.save(u);


            return t;

        } catch (Exception e) {

            log.fatal(e.getMessage(), e);
            return t;
        }

    }

    /**
     * 
     * @param points
     * @return
     */
    public Double sumDistance(List<Point> points) {

        Double tot = 0.0;
        Point prec = null;

        for (Point p : points) {

            if (prec == null) {
                prec = p;
                continue;
            }

            Double check_nan = p.getDistanceparent();

            if (check_nan == null || check_nan == Double.NaN || check_nan == 0.0 || check_nan < 0.0 || Double.isInfinite(check_nan) || Double.isNaN(check_nan)) {
                continue;
            }

            tot += check_nan;
            prec = p;
        }

        return tot;
    }

    /**
     * Calcolo il dislivello reale punto per punto
     * 
     * @param points
     * @return
     */
    public Double calculateRealElevation(List<Point> points) {

        Double tot = 0.0;
        Point prec = null;

        for (Point p : points) {

            if (prec == null) {
                prec = p;
                continue;
            }

            Double check_nan = Math.abs(prec.getEle() - p.getEle());

            if (check_nan == Double.NaN || check_nan == 0.0 || check_nan < 0.0 || Double.isInfinite(check_nan) || Double.isNaN(check_nan)) {
                continue;
            }

            tot += check_nan;
            prec = p;
        }

        return tot;
    }

    /**
     * Calcolo il dislivello come differenza tra il primo e l'ultimo punto
     * 
     * @param points
     * @return
     */
    public Double calculateElevation(List<Point> points) {
        return Math.abs(points.get(0).getEle() - points.get(points.size() - 1).getEle());
    }

    /**
     * 
     * @param track
     * @param username
     * @return
     */
    public Track add(Track track, String username) {
        track.setUser(userRepository.findOneByUsername(username));
        return this.add(track);
    }

    /**
     * 
     * @param track
     * @return
     */
    public Track save(Track track) {
        return trackRepository.save(track);
    }

    /**
    
     * @param username
     * @return
     */
    public List<Track> getAllByUsername(String username) {
        Query q = new Query(Criteria.where("user.username").is(username));
        log.debug("Query getAllByUsername: " + q.getQueryObject());
        System.err.println("Query getAllByUsername: " + q.getQueryObject());
        return mongoTemplate.find(q, Track.class);
    }

    /**
     * 
     * @param id
     * @return
     */
    public List<Track> getAllByUserId(String id) {
        Query q = new Query(
                Criteria.where("user.$id").is(new ObjectId(id)));
        log.debug("Query getAllByUserId : " + q.getQueryObject());
        System.err.println("Query getAllByUsername: " + q.getQueryObject());
        return mongoTemplate.find(q, Track.class);
    }

    /**
     * to do: mapreduce
     * 
     * @param id
     * @return
     */
    public Map getDistinctCountActivityByUserId(String id) {


        HashMap activity = new HashMap();

        Query q = new Query(
                Criteria.where("user.$id").is(new ObjectId(id)));

        List<Track> tracks = mongoTemplate.find(q, Track.class);

        if (tracks.isEmpty() || tracks == null) {
            return null;
        }

        for (Track t : tracks) {
            Integer n = 0;
            if (!activity.isEmpty() && activity.containsKey(t.getActivity())) {
                n = (Integer) activity.get(t.getActivity());
            }
            n++;
            activity.put(t.getActivity(), n);
        }


        return activity;


    }

    /**
     * 
     * @param track
     * @param tempDir
     * @return
     */
    public Track addMediaMap(Track track, File tempDir) {

        System.err.println("start addMediaMap");

        String param = "size=600x600&maptype=hybrid&sensor=false&path=color:0x0000ff|weight:3";

        int tot_ele = track.getTrackpoint().getPoints().size() / 50;
        int c = 0;
        String points = "|" + track.getStartpoint().getLoc().getX() + "," + track.getStartpoint().getLoc().getY();

        for (Point p : track.getTrackpoint().getPoints()) {
            c++;
            if (c == tot_ele) {
                points += "|" + p.getLoc().getX() + "," + p.getLoc().getY();
                c = 0;
            }
        }


        File mapfiletemp = new File(tempDir + "/" + System.currentTimeMillis() + ".png");
        System.err.println("mapfiletemp:" + mapfiletemp);

        HttpUtil.downloadData("http://maps.googleapis.com/maps/api/staticmap?" + param + points, mapfiletemp.toString());

        Media m = new Media();
        m.setFile(mapfiletemp);
        m.setObjid(track.getId());
        m.setObjtype("track");

        System.err.println("start add media to track");
        m = mediaService.addTrackMap(m);

        track.addMedia(m);
        return track;

    }

    /**
     * 
     * @param track
     * @param tempDir
     */
    public void addMassImportData(Track track, File tempDir) {
        try {

            String tmp_dir_new = tempDir + "/" + System.currentTimeMillis();
            File dir_target = new File(tmp_dir_new);

            System.err.println("dir_target:" + dir_target);

            FileUtils.forceMkdir(dir_target);
            List<String> list_file = ZipUtil.unzip(track.getFile().getInputStream(), dir_target.toString());

            if (list_file == null || list_file.isEmpty()) {
                return;
            }

            for (String f : list_file) {

                File file = new File(f);
                System.err.println(file.toString());
                Track new_track = new Track();
                new_track.setTags(track.getTags());
                new_track.setActivity(track.getActivity());
                new_track.setFileImported(file);
                this.add(new_track);
            }

        } catch (IOException ex) {
            log.fatal(ex, ex);
        }

    }

    private Track addGpx(String filetemp, Track t) {
        try {

            TrackPoint trackpoint = new TrackPoint();

            System.err.print("gpx");
            VTDGen vg = new VTDGen();
            vg.parseFile(filetemp, true);

            AutoPilot ap = null;
            VTDNav vn = vg.getNav();

            try {
                vn.toElement(VTDNav.ROOT);
                vn.matchElement("gpx");
                ap = new AutoPilot(vn);
                ap.selectXPath("/gpx/trk/trkseg/*");

            } catch (Exception ex) {
                log.fatal(ex.getMessage(), ex);

            }



            //VTDNav vn = vg.getNav();
            int r = 0;

            while ((r = ap.evalXPath()) != -1) {


                if (vn.toString(r).equals("trkpt")) {


                    VTDNav vn_tmp = vn.cloneNav();

                    double lat = Double.parseDouble(vn_tmp.toString(vn.getAttrVal("lat")));
                    double lon = Double.parseDouble(vn_tmp.toString(vn.getAttrVal("lon")));


                    if (lat > t.getMaxLat()) {
                        t.setMaxLat(lat);
                    }

                    if (lon > t.getMaxLon()) {
                        t.setMaxLon(lon);
                    }

                    if (t.getMinLat() == 0.0) {
                        t.setMinLat(lat);
                    }

                    if (t.getMinLon() == 0.0) {
                        t.setMinLon(lon);
                    }

                    if (lat < t.getMinLat()) {
                        t.setMinLat(lat);
                    }

                    if (lon < t.getMinLon()) {
                        t.setMinLon(lon);
                    }




                    VTDNav vntrkp = vn.cloneNav();

                    if (vntrkp.toElement(VTDNav.FC)) {
                        Point point = new Point();
                        Loc loc = new Loc();
                        loc.setX(lat);
                        loc.setY(lon);
                        point.setLoc(loc);
                        point.setLineId("-");


                        do {
                            String name = vntrkp.toString(vntrkp.getCurrentIndex());

                            if (name.equals("ele")) {

                                //log.debug("ele:" + vntrkp.toString(vntrkp.getText()));
                                double ele = Double.parseDouble(vntrkp.toString(vntrkp.getText()));
                                point.setEle(ele);
                                if (ele > t.getMaxAltitude()) {
                                    t.setMaxAltitude(ele);
                                }
                                if (t.getMinAltitude() == 0.0) {
                                    t.setMinAltitude(ele);
                                }
                                if (ele < t.getMinAltitude()) {
                                    t.setMinAltitude(ele);
                                }
                            } else if (name.equals("fix")) {

                                double fix = Double.parseDouble(vntrkp.toString(vntrkp.getText()));
                                point.setFix(fix);
                            } else if (name.equals("sat")) {
                                double sat = Double.parseDouble(vntrkp.toString(vntrkp.getText()));
                                point.setSat(sat);
                            } else if (name.equals("time")) {
                                String time = vntrkp.toString(vntrkp.getText());

                                point.setTime(GpsUtil.getDate(time));

                            }

                        } while (vntrkp.toElement(VTDNav.NEXT_SIBLING));

                        // point = pointService.add(point);
                        trackpoint.addPoint(point);

                    }


                }
            }



            trackPointRepository.save(trackpoint);
            t.setTrackpoint(trackpoint);

        } catch (XPathEvalException ex) {
            log.fatal(ex, ex);
        } catch (NavException ex) {
            log.fatal(ex, ex);
        }



        return t;



    }

    private Track addKml(String filetemp, Track t) {


        try {

            TrackPoint trackpoint = new TrackPoint();
            System.err.print("kml");

            VTDGen vg = new VTDGen();
            vg.parseFile(filetemp.toString(), true);

            AutoPilot ap = null;
            VTDNav vn = vg.getNav();

            try {
                vn.toElement(VTDNav.ROOT);
                vn.matchElement("kml");
                ap = new AutoPilot(vn);
                ap.selectXPath("/kml/Placemark/*");

            } catch (XPathParseException ex) {
                log.fatal(ex.getMessage(), ex);


            } catch (NavException ex) {
                log.fatal(ex.getMessage(), ex);

            }




            int r = 0;
            boolean first = true;
            while ((r = ap.evalXPath()) != -1) {
                //log.debug(vn.toString(r));

                if (vn.toString(r).equals("LineString")) {


                    VTDNav vnls = vn.cloneNav();

                    if (vnls.toElement(VTDNav.FC)) {

                        do {


                            if (vnls.toString(vnls.getCurrentIndex()).equals("coordinates")) {
                                String coordinates = vnls.toString(vnls.getText());

                                String[] lines = coordinates.split("\\n");

                                for (String l : lines) {
                                    if (l.length() == 0) {
                                        continue;
                                    }

                                    String[] block = l.split(",");

                                    Point point = t.getNewPoint();
                                    double ele = Double.parseDouble(block[2].trim());

                                    Loc loc = new Loc();
                                    loc.setX(Double.parseDouble(block[1].trim()));
                                    loc.setY(Double.parseDouble(block[0].trim()));
                                    point.setLoc(loc);
                                    point.setEle(ele);
                                    point.setLineId("-");

                                    if (first == true) {
                                        t.setStartpoint(point);
                                        first = false;
                                    }


                                    if (t.getMinAltitude() == 0.0) {
                                        t.setMinAltitude(ele);
                                    }

                                    if (ele > t.getMaxAltitude()) {
                                        t.setMaxAltitude(ele);
                                    }

                                    if (ele < t.getMinAltitude()) {
                                        t.setMinAltitude(ele);
                                    }

                                    trackpoint.addPoint(point);


                                }


                            }

                        } while (vnls.toElement(VTDNav.NEXT_SIBLING));

                    }


                }

            }
            trackPointRepository.save(trackpoint);
            t.setTrackpoint(trackpoint);

        } catch (XPathEvalException ex) {
            log.fatal(ex, ex);
        } catch (NavException ex) {
            log.fatal(ex, ex);
        }



        return t;

    }
    
    
    

        
        
}